package com.erbao.io.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;

/**
 * @author CaoBaoYin
 * @since 22/10/16 20:48
 */
public class MyNetty {


    @Test
    public void myBytebuf(){

//        ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(8, 20);
        //pool
//        ByteBuf buf = UnpooledByteBufAllocator.DEFAULT.heapBuffer(8, 20);
        ByteBuf buf = PooledByteBufAllocator.DEFAULT.heapBuffer(8, 20);
        print(buf);

        buf.writeBytes(new byte[]{1,2,3,4});
        print(buf);
        buf.writeBytes(new byte[]{1,2,3,4});
        print(buf);
        buf.writeBytes(new byte[]{1,2,3,4});
        print(buf);
        buf.writeBytes(new byte[]{1,2,3,4});
        print(buf);
        buf.writeBytes(new byte[]{1,2,3,4});
        print(buf);
        buf.writeBytes(new byte[]{1,2,3,4});
        print(buf);




    }

    public static void print(ByteBuf buf){
        System.out.println("buf.isReadable()    :"+buf.isReadable());
        System.out.println("buf.readerIndex()   :"+buf.readerIndex());
        System.out.println("buf.readableBytes() "+buf.readableBytes());
        System.out.println("buf.isWritable()    :"+buf.isWritable());
        System.out.println("buf.writerIndex()   :"+buf.writerIndex());
        System.out.println("buf.writableBytes() :"+buf.writableBytes());
        System.out.println("buf.capacity()  :"+buf.capacity());
        System.out.println("buf.maxCapacity()   :"+buf.maxCapacity());
        System.out.println("buf.isDirect()  :"+buf.isDirect());
        System.out.println("--------------");

    }

    /*
    客户端
    连接别人
    1，主动发送数据
    2，别人什么时候给我发？  event  selector
     */

    @Test
    public void loopExecutor() throws Exception {
        //group  线程池
        NioEventLoopGroup selector = new NioEventLoopGroup(2);
        selector.execute(()->{
            try {
                for (;;){
                    System.out.println("hello world001");
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        selector.execute(()->{
            try {
                for (;;){
                    System.out.println("hello world002");
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });


        System.in.read();
    }

    @Test
    public void clientMode() throws InterruptedException {
        NioSocketChannel client = new NioSocketChannel();

        // java.lang.IllegalStateException: channel not registered to an event loop
        NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup(1);
        nioEventLoopGroup.register(client);

        /*读取数据 Channel channel = client.read(); 发现方法返回的是channel，并非读到的内容
         * 回想java的多路复用，先有事件，后有读写操作
         * netty读取数据采用pipeline，（数据通过管道可能需要解压，解密，组装等操作才是最终我们想要的数据。可以将这一系列的操作的handler添加到pipeline中）*/
        ChannelPipeline clientPipeLine = client.pipeline();
        clientPipeLine.addLast(new MyInHandler());// 响应式

        ChannelFuture connectFuture = client.connect(new InetSocketAddress("172.20.10.2", 9090));
        ChannelFuture sync = connectFuture.sync();

        ByteBuf byteBuf = Unpooled.copiedBuffer("hello server".getBytes());
        ChannelFuture writeFuture = client.writeAndFlush(byteBuf);
        ChannelFuture send = writeFuture.sync();

        // 等着channel关闭
        sync.channel().closeFuture().sync();

        System.out.println("client over……");
        /*try {
            System.in.read();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }*/
    }

    @Test
    public void serverMode() throws IOException {
        NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup(1);

        NioServerSocketChannel server = new NioServerSocketChannel();

        ChannelPipeline pipeline = server.pipeline();
        pipeline.addLast(new MyAcceptHandler(nioEventLoopGroup, new ChannelInit()));// accept 接收客户端，并注册客户端，然后处理客户端读写（响应式客户端handler）

        nioEventLoopGroup.register(server);

        ChannelFuture bindFuture = server.bind(new InetSocketAddress("172.20.10.3", 9090));

        try {
            bindFuture.sync().channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        // System.in.read();
    }

    @Test
    public void nettyClient() throws InterruptedException {
        NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup(1);

        Bootstrap bootstrap = new Bootstrap();
        ChannelFuture connectFuture = bootstrap.channel(NioSocketChannel.class)
                .group(nioEventLoopGroup)
                // .handler(new MyInHandler()) // 直接加handler
                // .handler(new ChannelInit()) // 自己写的桥
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new MyInHandler());
                    }
                }) // netty 官方的桥的写法
                .connect(new InetSocketAddress("172.20.10.2", 9090));

        Channel client = connectFuture.sync().channel();// 等待连接成功后获取到channel

        ByteBuf byteBuf = Unpooled.copiedBuffer("hello server".getBytes());
        ChannelFuture writeFuture = client.writeAndFlush(byteBuf);
        ChannelFuture send = writeFuture.sync();

        client.closeFuture().sync(); // 阻塞等待关闭

    }

    @Test
    public void nettyServer() throws InterruptedException {
        NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup(1);

        ServerBootstrap bootstrap = new ServerBootstrap();
        ChannelFuture bindFuture = bootstrap.group(nioEventLoopGroup, nioEventLoopGroup)
                .channel(NioServerSocketChannel.class)
                // .handler() 不用自己写accept handler
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new MyInHandler());
                    }
                })// 接收的client的handler
                .bind(new InetSocketAddress("172.20.10.3", 9090));

        bindFuture.sync().channel().closeFuture().sync();
    }
}

@ChannelHandler.Sharable
class ChannelInit extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel init registered...");
        Channel channel = ctx.channel();
        ChannelPipeline pipeline = channel.pipeline();
        pipeline.addLast(new MyInHandler()); // MyInHandler 无法设置成单例的情况下，需要ChannelInit搭桥每次都new（见netty源码，真是代码不是这样写的）
        pipeline.remove(this); // ChannelInit 因为只是搭桥的作用，添加真正需要的handler之后，可以“过河拆桥”
    }
}

class MyAcceptHandler extends ChannelInboundHandlerAdapter {
    private final EventLoopGroup eventLoopGroup;
    private final ChannelHandler handler;

    public MyAcceptHandler(EventLoopGroup eventLoopGroup, ChannelHandler myInHandler) {
        this.eventLoopGroup = eventLoopGroup;
        this.handler = myInHandler;
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("server  registed...");
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("server active...");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // server 端 listen的socket读到的一定是socketchannel
        NioSocketChannel client = (NioSocketChannel) msg;

        // 客户端的事件 响应式的handler
        ChannelPipeline pipeline = client.pipeline();
        pipeline.addLast(handler);// 如果每次new一个新的则不存在共享问题，但是无法从外界传参。如果必须每次都new，可以创建一个让用户去实现的类

        // 注册
        this.eventLoopGroup.register(client);

    }
}

/*客户端读写数据处理的handler 一般都是用户自己写的
* 如果直接设计成单例的。则单例中不能有相关属性，会多线程并发问题。用户自己写的handler就要强制用户不能写属性
* 单例handler。不涉及业务就能重用。接收多个客户端时，客户端使用同样的响应式handler */
class MyInHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("client  registed...") ;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("client active...");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 刚读到数据，进入pipeline遇到的第一个handler msg就是netty自己封装的ByteBuf存储的
        ByteBuf byteBuf = (ByteBuf) msg;
        /*打印数据从msg中读取，ByteBuf 一次可以读取不同类型，从而一次读取多少字节。如果是发的一个字符串怎么一下子都读出来呢
        * String s = byteBuf.read 并未找到方法
        * 但是有读CharSequence的方法 长度就是可以读的长度*/
        // readCharSequence 会移动bytebuf的指针。writeAndFlush(byteBuf) 指针已经到最后会读不到内容
        CharSequence readCharSequence = byteBuf.getCharSequence(0, byteBuf.readableBytes(), StandardCharsets.UTF_8);

        System.out.print(readCharSequence);

        ctx.writeAndFlush(byteBuf);

    }
}


